/**
 * Copyright ©2016-2020 northkingbpo Corporation, All Rights Reserved
 * http://www.northkingbpo.net/
 */
package profile.hujia.algorithm.iteration;

import lombok.extern.slf4j.Slf4j;
import profile.hujia.algorithm.parallel.ParticleSwarmOptimizationModeOneMove;

import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;

import static profile.hujia.algorithm.common.ParticleSwarmOptimizationFunction.adjustFeasibleSolution;
import static profile.hujia.algorithm.common.ParticleSwarmOptimizationFunction.getScore;

/**
 * class info
 *
 * @author hujia
 * @date 2019-01-25 10:28
 */
@Slf4j
public class ParticleSwarmOptimizationModeOne {

    public static final int PARTICLE_NUMBER = 50;
    //-1,0,1
    private static final int MAX_VELOCITY = 3;
    private int taskNumber;
    public static final int PARALLEL_NUMBER = 5;

    private int[][] constraintCondition;

    private Map<String, Integer> score = new HashMap<>();
    private Map<Integer, Integer> constraintUser = new HashMap<>();
    private int[][] feasibleSolutionSwarm;
    private double[][] velocitySwarm;
    private int[] globalBestSolution;
    private int[][] particleBestSolution;
    private int[] particleBestSolutionScore;
    private int globalBestSolutionScore = 0;
    private int bestPosition;
    private ThreadPoolExecutor executor = new ThreadPoolExecutor(PARALLEL_NUMBER, PARALLEL_NUMBER,
            0L, TimeUnit.MICROSECONDS, new LinkedBlockingQueue<Runnable>());

    //control convergence. the smaller value,the faster convergence.
    public static final double W_MAX = 0.65;
    public static final double W_MIN = 0.4;

    //self
    public static final double C_1 = 0.6;

    //society
    public static final double C_2 = 0.4;

    private int numberOfIterations = 200;

    public ParticleSwarmOptimizationModeOne(int[][] constraintCondition, int taskNumber) {
        this.constraintCondition = constraintCondition;
        this.taskNumber = taskNumber;
    }

    public void init() {
        //constraintCondition = new int[taskNumber][];
        feasibleSolutionSwarm = new int[PARTICLE_NUMBER][];
        velocitySwarm = new double[PARTICLE_NUMBER][];
        globalBestSolution = new int[taskNumber];
        particleBestSolution = new int[PARTICLE_NUMBER][];
        particleBestSolutionScore = new int[PARTICLE_NUMBER];
        for (int i = 1; i <= 10; ++i) {
            for (int j = 1; j <= taskNumber; ++j) {
                score.put(String.format("%04d%04d", i, j), 1);
            }
        }
    }

    public void run() {
        int k = 0;
        String result = "";
        algorithmInitial();
        double random1;
        double random2;
        while (k < numberOfIterations) {
            CountDownLatch countDownLatch = new CountDownLatch(PARALLEL_NUMBER);
            result = "";
            Random random = new Random();
            random1 = random.nextDouble();
            random2 = random.nextDouble();
            for (int i = 0; i < PARALLEL_NUMBER; ++i) {
                int begin = (PARTICLE_NUMBER / PARALLEL_NUMBER) * i;
                int end = (PARTICLE_NUMBER / PARALLEL_NUMBER) * (i + 1) - 1;
                ParticleSwarmOptimizationModeOneMove move = new ParticleSwarmOptimizationModeOneMove(taskNumber, random1, random2,
                        begin, end, velocitySwarm, feasibleSolutionSwarm, constraintCondition, particleBestSolution,
                        globalBestSolution, particleBestSolutionScore, score, countDownLatch);
                executor.execute(move);
            }
            try {
                countDownLatch.await();
            } catch (Exception e) {
                log.error("wait error.");
            }
            algorithmStatistic();
            ++k;
            for (int i = 0; i < taskNumber; ++i) {
                int index = globalBestSolution[i];
                result = result + ',' + constraintCondition[i][index];
            }
            log.info(String.format("k is %d,current highest score is |%d|,%s"
                    , k, globalBestSolutionScore, result));
        }
        executor.shutdown();
        result = "";
        for (int i = 0; i < taskNumber; ++i) {
            int index = globalBestSolution[i];
            result = result + ',' + constraintCondition[i][index];
        }
        log.info("the highest score is " + globalBestSolutionScore);
        log.info("the task to user is " + result);
    }

    private void algorithmInitial() {
        for (int i = 0; i < PARTICLE_NUMBER; ++i) {
            feasibleSolutionSwarm[i] = randomCreateInitialFeasibleSolution();
            adjustFeasibleSolution(feasibleSolutionSwarm[i], constraintUser, constraintCondition, taskNumber);
            particleBestSolution[i] = feasibleSolutionSwarm[i].clone();
            velocitySwarm[i] = randomCreateInitialVelocity();
            particleBestSolutionScore[i] = getScore(feasibleSolutionSwarm[i], constraintCondition, score, taskNumber);
        }
        int globalBestSolutionIndex = getGlobalBest(particleBestSolutionScore);
        globalBestSolution = particleBestSolution[globalBestSolutionIndex];
    }


    private void algorithmStatistic() {
        for (int i = 0; i < PARTICLE_NUMBER; ++i) {
            if (globalBestSolutionScore < particleBestSolutionScore[i]) {
                globalBestSolutionScore = particleBestSolutionScore[i];
                globalBestSolution = feasibleSolutionSwarm[i].clone();
                bestPosition = i;
            }
        }
    }

    private int[] randomCreateInitialFeasibleSolution() {
        Random random = new Random();
        int[] feasibleSolution = new int[taskNumber];
        int maxIndex;
        int index;
        int k = 0;
        constraintUser.clear();
        while (k < constraintCondition.length) {
            maxIndex = constraintCondition[k].length;
            if (maxIndex == 1) {
                feasibleSolution[k] = 0;
            } else {
                index = random.nextInt(maxIndex - 1) + 1;
                feasibleSolution[k] = index;
            }
            ++k;
        }
        return feasibleSolution;
    }

    private double[] randomCreateInitialVelocity() {
        Random random = new Random();
        double[] velocity = new double[taskNumber];
        for (int i = 0; i < taskNumber; ++i) {
            velocity[i] = random.nextInt(MAX_VELOCITY) - 1;
        }
        return velocity;
    }

    private int getGlobalBest(int[] particleBestSolutionScore) {
        int pos = 0;
        for (int i = 0; i < PARTICLE_NUMBER; ++i) {
            if (globalBestSolutionScore < particleBestSolutionScore[i]) {
                globalBestSolutionScore = particleBestSolutionScore[i];
                pos = 0;
            }
        }
        return pos;
    }
}

